package fr.babos.graphic.components.bitmaps 
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.PixelSnapping;
	import flash.display.Shape;
	import flash.geom.Matrix;
	import fr.babos.graphic.components.base.ResizableComponent;
	
	/**
	 * @author ZoulouX
	 */
	public class AdvancedBitmap extends ResizableComponent 
	{
		/**
		 * Changement du mode de rendu, le rendu devient un bitmap et l'image sera stretch
		 */
		public static const BITMAP_RENDER_MODE			:String 		= "bitmapRenderMode";
		
		/**
		 * Pas de scaling appliqué (uniquement la densité)
		 */
		public static const NO_SCALE_MODE				:String 		= "noScaleMode";
		
		/**
		 * On est en mode repeat (pas de scale)
		 */
		public static const REPEAT_SCALE_MODE			:String 		= "repeatScaleMode";
		
		/**
		 * Etirer l'image sur les 2 dimensions
		 */
		public static const STRECH_SCALE_MODE			:String 		= "strechScaleMode";
		
		/**
		 * Centrer l'image sans scaling
		 */
		public static const CENTER_SCALE_MODE			:String 		= "centerScaleMode";
		
		/**
		 * Scaler l'image par l'intérieur (bordures visibles)
		 */
		public static const INSIDE_SCALE_MODE			:String 		= "insideScaleMode";
		
		/**
		 * Scaler l'image par l'exérieur (image rognée)
		 */
		public static const OUTSIDE_SCALE_MODE			:String 		= "outsideScaleMode";
		
		/**
		 * Le composant fait la taille de l'image
		 */
		public static const AUTO_SIZE_SCALE_MODE		:String 		= "autoSizeScaleMode";
		
		
		/**
		 * Le bitmapData pour le fond (null pour utiliser la couleur de fond)
		 */
		protected var _bitmapData						:BitmapData;
		
		/**
		 * Le mode de redimensionnement de l'image
		 */
		protected var _renderMode						:String;
		
		/**
		 * La couleur de fond (blanc par défaut)
		 */
		protected var _backgroundColor					:int;
		
		/**
		 * La transparence de la couleur de fond
		 */
		protected var _backgroundAlpha					:Number			= 1;
		
		/**
		 * La taille du contour (0 pour pas de contour)
		 */
		protected var _outlineSize						:Number;
		
		/**
		 * La couleur du contour (noir par défaut)
		 */
		protected var _outlineColor						:uint			= 0x000000;
		
		/**
		 * L'alpha du contour
		 */
		protected var _outlineAlpha						:Number			= 1;
		
		/**
		 * Arrondi des coins (0 pour dessiner un rectangle)
		 */
		protected var _cornerRadius						:uint			= 0;
		
		/**
		 * Lisser l'image
		 */
		protected var _smooth							:Boolean		= true;
		
		/**
		 * La matrice de transformation
		 */
		protected var _matrix							:Matrix			= new Matrix();
		
		/**
		 * Autoriser les dépassements sur les mode INSIDE et OUTSIDE
		 */
		protected var _allowOverflow					:Boolean		= false;
		
		/**
		 * Décallage du dessin en X
		 */
		protected var _xDrawDecay						:Number;
		
		/**
		 * Décallage du dessin en Y
		 */
		protected var _yDrawDecay						:Number;
		
		/**
		 * Le bitmap pour le rendu bitmap
		 */
		protected var _bitmap							:Bitmap;
		
		/**
		 * La densité de l'image. Par exemple si l'image est en format retina, la densité sera de 2.
		 */
		protected var _density							:Number;
		
		/**
		 * Le décallage horizontal de l'image
		 */
		protected var _bitmapHorizontalOffset			:int;
		
		/**
		 * Le décallage vertical de l'image
		 */
		protected var _bitmapVerticalOffset				:int;
		
		
		/**
		 * Le bitmapData pour le fond (null pour utiliser la couleur de fond)
		 */
		public function get bitmapData ():BitmapData { return _bitmapData; }
		public function set bitmapData (value:BitmapData):void 
		{
			// Si c'est différent
			if (_bitmapData != value)
			{
				// Enregistrer la référence
				_bitmapData = value;
				
				// Si on a un bitmap, on lui applique
				if (_bitmap != null)
					_bitmap.bitmapData = _bitmapData;
				
				// Rafraichir
				redraw();
			}
		}
		
		/**
		 * Le mode de redimensionnement de l'image
		 */
		public function get renderMode ():String { return _renderMode; }
		public function set renderMode (value:String):void 
		{
			// Si on est en mode de rendu bitmap
			if (value == BITMAP_RENDER_MODE)
			{
				// On créé le bitmap porteur
				_bitmap = new Bitmap(_bitmapData, PixelSnapping.ALWAYS, _smooth);
				
				// Et on l'ajoute
				addChild(_bitmap);
			}
			
			// Si on n'est plus en mode bitmap mais qu'il reste un bitmap
			else if (_bitmap != null)
			{
				// On le vire
				removeChild(_bitmap);
				_bitmap = null;
			}
			
			// Enregistrer le nouveau mode
			_renderMode = value;
			
			// Actualiser
			redraw();
		}
		
		/**
		 * La couleur de fond (blanc par défaut)
		 */
		public function get backgroundColor ():int { return _backgroundColor; }
		public function set backgroundColor (value:int):void 
		{
			_backgroundColor = value;
			redraw();
		}
		
		/**
		 * La transparence de la couleur de fond
		 */
		public function get backgroundAlpha ():Number { return _backgroundAlpha; }
		public function set backgroundAlpha (value:Number):void 
		{
			_backgroundAlpha = value;
			redraw();
		}
		
		/**
		 * La taille du contour (0 pour pas de contour)
		 */
		public function get outlineSize ():Number { return _outlineSize; }
		public function set outlineSize (value:Number):void 
		{
			_outlineSize = value;
			redraw();
		}
		
		/**
		 * La couleur du contour (noir par défaut)
		 */
		public function get outlineColor ():uint { return _outlineColor; }
		public function set outlineColor (value:uint):void 
		{
			_outlineColor = value;
			redraw();
		}
		
		/**
		 * L'alpha du contour
		 */
		public function get outlineAlpha ():Number { return _outlineAlpha; }
		public function set outlineAlpha (value:Number):void 
		{
			_outlineAlpha = value;
			redraw();
		}
		
		/**
		 * Arrondi des coins (0 pour dessiner un rectangle)
		 */
		public function get cornerRadius ():uint { return _cornerRadius; }
		public function set cornerRadius (value:uint):void 
		{
			_cornerRadius = value;
			redraw();
		}
		
		/**
		 * Lisser l'image
		 */
		public function get smooth ():Boolean { return _smooth; }
		public function set smooth (value:Boolean):void 
		{
			_smooth = value;
			
			if (_bitmap != null)
			{
				_bitmap.smoothing = _smooth;
			}
			
			redraw();
		}
		
		/**
		 * Autoriser les dépassements sur les mode INSIDE et OUTSIDE
		 */
		public function get allowOverflow ():Boolean { return _allowOverflow; }
		public function set allowOverflow (value:Boolean):void 
		{
			_allowOverflow = value;
		}
		
		/**
		 * La densité de l'image. Par exemple si l'image est en format retina, la densité sera de 2.
		 */
		public function get density ():Number { return _density; }
		public function set density (value:Number):void
		{
			_density = value;
			
			redraw();
		}
		
		/**
		 * Le décallage horizontal de l'image
		 */
		public function get bitmapHorizontalOffset ():int { return _bitmapHorizontalOffset; }
		public function set bitmapHorizontalOffset (value:int):void
		{
			_bitmapHorizontalOffset = value;
			
			redraw();
		}
		
		/**
		 * Le décallage vertical de l'image
		 */
		public function get bitmapVerticalOffset ():int { return _bitmapVerticalOffset; }
		public function set bitmapVerticalOffset (value:int):void
		{
			_bitmapVerticalOffset = value;
			
			redraw();
		}
		
		
		/**
		 * Le constructeur
		 * @param	pBitmapData : Le bitmapData a afficher (optionnel, une couleur unie sera affichée si null)
		 * @param	pRenderMode : Le mode de rendu du bitmap (voir static)
		 * @param	pDensity : La densité de l'image. Par exemple si l'image est en format retina, la densité sera de 2. Ne servira que les modes de rendu AUTO_SIZE_SCALE_MODE et REPEAT_SCALE_MODE
		 * @param	pBackgroundColor La couleur de fond (optionnel, rien ne sera affiché si -1)
		 */
		public function AdvancedBitmap (pBitmapData:BitmapData = null, pRenderMode:String = INSIDE_SCALE_MODE, pDensity:Number = 1, pBackgroundColor:int = -1)
		{
			_bitmapData = pBitmapData;
			_backgroundColor = pBackgroundColor;
			_density = pDensity;
			renderMode = pRenderMode;
		}
		
		/**
		 * Les dimensions de l'élément ont changées
		 */
		override protected function resized ():void
		{
			// Redessiner
			redraw();
		}
		
		/**
		 * Définir le décallage du bitmap
		 * @param	pHorizontalOffset : Le décallage horizontal
		 * @param	pVerticalOffset : Le décallage vertical
		 */
		public function bitmapOffset (pHorizontalOffset:int, pVerticalOffset:int):AdvancedBitmap
		{
			// Enregistrer les valeurs
			_bitmapHorizontalOffset = pHorizontalOffset;
			_bitmapVerticalOffset = pVerticalOffset;
			
			// Actualiser
			redraw();
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * La couleur de fond
		 * @param	pBackgroundColor : La couleur (-1 pour transparent)
		 * @param	pBackgroundAlpha : La transparence du fond
		 */
		public function background (pBackgroundColor:int, pBackgroundAlpha:Number):AdvancedBitmap
		{
			// Enregistrer
			_backgroundColor = pBackgroundColor;
			_backgroundAlpha = pBackgroundAlpha
			
			// Actualiser
			redraw();
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Le contour
		 * @param	pOutlineSize : La taille du contour (0 ou NaN pour aucun contour)
		 * @param	pOutlineColor : La couleur du contour
		 * @param	pOutlineAlpha : La transparence du contour
		 */
		public function outline (pOutlineSize:Number, pOutlineColor:uint = 0, pOutlineAlpha:Number = 1):AdvancedBitmap
		{
			// Enregistrer
			_outlineSize = pOutlineSize;
			_outlineColor = pOutlineColor;
			_outlineAlpha = pOutlineAlpha;
			
			// Actualiser
			redraw();
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Bordures arrondis
		 * @param	pRadius
		 * @return
		 */
		public function radius (pRadius:uint):AdvancedBitmap
		{
			// Enregistrer
			_cornerRadius = pRadius;
			
			// Actualiser
			redraw();
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Forcer le rafraichissement
		 */
		public function redraw ():void
		{
			// Virer l'ancienne image
			graphics.clear();
			
			// Si c'est la taille de l'image qui défini la taille du composant
			if (_renderMode == AUTO_SIZE_SCALE_MODE && _bitmapData != null)
			{
				// On récupère la taille et on l'applique
				size(_bitmapData.width / _density, _bitmapData.height / _density);
			}
			
			// Si on a un bitmap
			if (_bitmap != null)
			{
				// On lui applique les dimensions
				_bitmap.width = _localWidth;
				_bitmap.height = _localHeight;
			}
			
			// Si on a des dimensions
			else if (_localWidth > 0 && _localHeight > 0)
			{
				// Si on a un contour
				if (_outlineSize > 0)
				{
					// On dessine le contour
					graphics.lineStyle(_outlineSize, _outlineColor, _outlineAlpha, true);
				}
				
				// Si on a un bitmapData
				if (_bitmapData != null)
				{
					// Actualiser la matrice
					updateMatrix();
					
					// Si on a une couleur de fond
					// Et si on est sur un mode de rendu ou le contenu peut être plus petit que la zone d'affichage
					if (_backgroundColor != -1 && _renderMode != REPEAT_SCALE_MODE && _renderMode != AUTO_SIZE_SCALE_MODE && _renderMode != STRECH_SCALE_MODE)
					{
						// On commence le déssin
						graphics.beginFill(_backgroundColor, _backgroundAlpha);
						
						// Dessiner un rectangle avec ou sans bords arrondis
						if (_cornerRadius == 0)
						{
							// Sans bords arrondis
							graphics.drawRect(
								0,
								0,
								_localWidth,
								_localHeight
							);
						}
						else
						{
							// Avec bords arrondis
							graphics.drawRoundRect(
								0,
								0,
								_localWidth,
								_localHeight,
								_cornerRadius, _cornerRadius
							);
						}
						
						// Arrêter le style de trait et le remplissage couleur
						graphics.lineStyle(NaN);
						graphics.endFill();
					}
					
					// On dessiner le bitmap avec la nouvelle matrice
					graphics.beginBitmapFill(_bitmapData, _matrix, _renderMode == REPEAT_SCALE_MODE, _smooth);
				}
				else
				{
					// Sinon on n'a pas de dépassement
					_xDrawDecay = _yDrawDecay = 0;
					
					// Démarrer le dessin avec une couleur pleine
					if (_backgroundColor != -1)
						graphics.beginFill(_backgroundColor, _backgroundAlpha);
				}
				
				// Dessiner un rectangle avec ou sans bords arrondis
				if (_cornerRadius == 0)
				{
					// Sans bords arrondis
					graphics.drawRect(
						_xDrawDecay,
						_yDrawDecay,
						_localWidth - _xDrawDecay * 2,
						_localHeight - _yDrawDecay * 2
					);
				}
				else
				{
					// Avec bords arrondis
					graphics.drawRoundRect(
						_xDrawDecay,
						_yDrawDecay,
						_localWidth - _xDrawDecay * 2,
						_localHeight - _yDrawDecay * 2,
						_cornerRadius, _cornerRadius
					);
				}
				
				// Terminer le dessin
				graphics.endFill();
			}
		}
		
		/**
		 * Actualiser la matrice selon le mode de redimensionnement
		 */
		protected function updateMatrix ():void
		{
			// Replacer la matrice à 0
			_matrix.identity();
			
			// Les scales par défaut
			var horizontalScale	:Number	= 1;
			var verticalScale	:Number	= 1;
			
			// Calculer le scale
			if (_renderMode == STRECH_SCALE_MODE)
			{
				// Scaler au composant
				horizontalScale = _localWidth / _bitmapData.width;
				verticalScale = _localHeight / _bitmapData.height;
			}
			else if (_renderMode == INSIDE_SCALE_MODE || _renderMode == OUTSIDE_SCALE_MODE)
			{
				// On applique les 2 dimensions sur le ratio horizontal
				verticalScale = horizontalScale = _localWidth / _bitmapData.width;
				
				// Et on regarde le comportement du ratio vertical
				if (
						_renderMode == INSIDE_SCALE_MODE && _bitmapData.height * verticalScale > _localHeight
						||
						_renderMode == OUTSIDE_SCALE_MODE && _bitmapData.height * verticalScale < _localHeight
					)
				{
					// On appliquer les 2 dimensions sur le ratio vertical
					verticalScale = horizontalScale = _localHeight / _bitmapData.height;
				}
			}
			
			// Appliquer la densité pour ces modes de rendu
			else if (_renderMode == AUTO_SIZE_SCALE_MODE || _renderMode == REPEAT_SCALE_MODE || _renderMode == NO_SCALE_MODE)
			{
				horizontalScale = 1 / _density;
				verticalScale = 1 / _density;
			}
			
			// Appliquer le scale
			_matrix.scale(horizontalScale, verticalScale);
			
			// Si on doit centrer
			if (_renderMode == CENTER_SCALE_MODE || _renderMode == INSIDE_SCALE_MODE || _renderMode == OUTSIDE_SCALE_MODE)
			{
				// On centre par rapport aux précédentes modifications du scale
				_matrix.translate(
					_localWidth / 2 - _bitmapData.width * horizontalScale / 2 + _bitmapHorizontalOffset,
					_localHeight / 2 - _bitmapData.height * verticalScale / 2 + _bitmapVerticalOffset
				);
				
				// Si on autorise pas les dépassements de dessin de l'image
				if (_allowOverflow || _renderMode == INSIDE_SCALE_MODE)
				{
					// On enregistre les dépassements
					_xDrawDecay = _matrix.tx;
					_yDrawDecay = _matrix.ty;
				}
				else
				{
					// Sinon on n'a pas de dépassement
					_xDrawDecay = _yDrawDecay = 0;
				}
			}
			else
			{
				// Sinon on n'a pas de dépassement
				_xDrawDecay = _yDrawDecay = 0;
				
				// Déplacer selon les offsets
				_matrix.translate(_bitmapHorizontalOffset, _bitmapVerticalOffset);
			}
		}
	}
}